package org.wattdepot.gdata.kukuicup.updater.energygoalgame;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.gdata.kukuicup.service.GDataService;
import org.wattdepot.gdata.kukuicup.updater.LastThirtyDaysViewBetaEval;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Provides an in-memory store of the actual consumption for a given Lounge for today and over
 * the last seven days.
 * This is used because WattDepot does not provide values for the beta test.
 * This mechanism assumes that updateActualConsumption() will be called every 6 minutes (10 times
 * per hour).
 * @author Philip Johnson
 */
public class BetaEvalActualConsumption {
  /** The lounge. */
  private String lounge; 
  /** Stores the simulated actual consumption for a source for today and the prior N days. */
  private Map<XMLGregorianCalendar, Integer> loungeActuals = 
    new HashMap<XMLGregorianCalendar, Integer>(); 
  /** For generating random hourly usage data. */
  private  Random random = new Random();
  /** The file containing user data. */
  private String userDataFileName;
  /** The GDataService, used for getting the LastThirtyDays view. */
  private GDataService gdataService;

  /**
   * Creates a new consumption collection and initializes the actual consumption data.
   * @param lounge The lounge.
   * @param userDataFileName The file containing user data.
   * @param gdataService The gdataservice. 
   */
  public BetaEvalActualConsumption(String lounge, String userDataFileName, 
      GDataService gdataService) {
    this.lounge = lounge;
    this.userDataFileName = userDataFileName;
    this.gdataService = gdataService;
    initializeActuals();  //NOPMD
  }
  
  /**
   * Returns the consumption for the specified day.
   * @param day The day of interest.
   * @return The consumption for this day.
   */
  public int getConsumption(XMLGregorianCalendar day) {
    if (!loungeActuals.containsKey(day)) {
      throw new RuntimeException("Requested data for a non-existent day" + day);
    }
    return loungeActuals.get(day);
  }

  /**
   * Initializes the historical data for this lounge from the EnergyLastThirtyDaysBetaEval
   * spreadsheet.
   */
  private void initializeActuals() {
    XMLGregorianCalendar tstamp = makeDateStamp();
    LastThirtyDaysViewBetaEval view = new LastThirtyDaysViewBetaEval(gdataService);
    List<Integer> historicalData = view.getEnergyHistory(this.lounge);
    for (int day = 0; day < historicalData.size(); day++) {
      XMLGregorianCalendar currDay = Tstamp.incrementDays(tstamp, -day);
      loungeActuals.put(currDay, historicalData.get(day));
    }
  }
  
  /**
   * Updates the actual consumption for the given lounge, under the assumption that this method
   * is called exactly ten times per hour (every six minutes).
   * Each time this method is called, the actual consumption for this six minute period will be
   * calculated as a random number between 400 and 600 minus the "behavioral term", which reduces
   * energy consumption based upon the lounge's cumulative interaction with the website. 
   */
  public void updateActualConsumption() {
    XMLGregorianCalendar today = makeDateStamp();
    // Make sure we have a value for today in case it's just after midnight of a new day.
    if (!this.loungeActuals.containsKey(today)) {
      this.loungeActuals.put(today, 0);
    }
    // Get a random number between -100 and +100.
    int randomFactor = random.nextInt(200) - 100;
    // Determine this hour's consumption.
    int behavioralTerm = computeBehavioralTerm();
    int sixMinuteConsumption = 500 + randomFactor - behavioralTerm;
    int cumulativeToday = sixMinuteConsumption + this.loungeActuals.get(today);
    //System.out.format("EnergyGoalGameBetaEval:  %s: random: %s behavior: %s sixMin: %s cum: %s%n",
    //    lounge, randomFactor, behavioralTerm, sixMinuteConsumption, cumulativeToday); 
    // Now update the cumulative energy use for this lounge for the current day.
    this.loungeActuals.put(today, cumulativeToday);
  }
  
  /**
   * Calculates and returns the "behavioral term", which is a measure of lounge membership usage
   * of the website.   The behavioral term is calculated as:
   *   [(team points collected * 2) + 
   *    (10 * number of user-initiated wall postings) + 
   *    (100 * number of Canopy members)]
   *  all divided by 10 (to get the term for a six minute interval)
   *  with a maximum value of 250 (to ensure that consumption is always positive).
   *  Those three values are read in from a file generated by Makahiki. 
   * @return The behavioral term value. 
   */
  private int computeBehavioralTerm() {
    BetaEvalUserData userData = new BetaEvalUserData(this.userDataFileName);
    int term = 
      ((userData.getTotalPoints(lounge) * 2) +
      (userData.getUserPosts(lounge) * 10) +
      (userData.getCanopyMembers(lounge) * 100)) / 10;
    return Math.min(250, term);
  }
  
  /**
   * Makes today's date with hours, minutes, seconds, and milliseconds set to zero.
   * @return Today's date stamp.
   */
  public XMLGregorianCalendar makeDateStamp() {
    XMLGregorianCalendar tstamp = Tstamp.makeTimestamp();
    tstamp.setHour(0);
    tstamp.setMinute(0);
    tstamp.setSecond(0);
    tstamp.setMillisecond(0);
    return tstamp;
  }
}
